package edu.ssdut.revise.utils;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.nodeTypes.NodeWithName;
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
import com.github.javaparser.serialization.JavaParserJsonSerializer;
import edu.ssdut.revise.exceptions.ParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.json.Json;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

public class javaParserUtils {
    private final static Logger logger = LoggerFactory.getLogger(javaParserUtils.class);

    /**
     * convert file to {@link CompilationUnit}, used in {@link edu.ssdut.revise.core.match.CUMatcher}
     *
     * @param file java source file, <b>please make sure legal java source file passed</b>
     * @return {@link CompilationUnit} or null if the file passed does not exist.
     */
    public static CompilationUnit file2CU(File file) throws ParserException {
        try {
            return StaticJavaParser.parse(file);
        } catch (ParseProblemException ppe) {
            logger.error("ParseProblemException encountered, syntax error maybe!\n" + file.getAbsolutePath());
            throw new ParserException(file.getAbsolutePath() + "\n" + ppe.getProblems().toString());
        } catch (FileNotFoundException e) {
            logger.error("FILE NOT FOUND IN file2CU");
            return null;
        }
    }

    public static CompilationUnit string2CU(String content) throws ParserException {
        try {
            return StaticJavaParser.parse(content);
        } catch (ParseProblemException ppe) {
            logger.error("ParseProblemException encountered, syntax error maybe!\n" + content);
            throw new ParserException(ppe.getProblems().toString());
        }
    }

    public static String getNodeName(Node node) {
        if (node instanceof NodeWithName<?>) {
            NodeWithName<?> nodeWithName = (NodeWithName<?>) node;
            return nodeWithName.getNameAsString();
        } else if (node instanceof NodeWithSimpleName) {
            NodeWithSimpleName<?> nodeWithSimpleName = (NodeWithSimpleName<?>) node;
            return nodeWithSimpleName.getNameAsString();
        } else if (node instanceof FieldDeclaration) {
            // TODO not a good solution to resolve fields
            FieldDeclaration field = (FieldDeclaration) node;
            StringBuilder sb = new StringBuilder();
            if (field.getModifiers().size() > 0) {
                field.getModifiers().forEach(m -> sb.append(m).append(" "));
                sb.delete(sb.length() - 1, sb.length());
            }
            field.getVariables().forEach(v -> sb.append(v).append(", "));
            sb.delete(sb.length() - 2, sb.length());
            return sb.toString();
        } else {
            logger.warn("unknown node with no name: " + node);
            return "unknown node with no name";
        }

    }

    public static String nodeSerialize(Node node, boolean prettyPrint) {
        Map<String, ?> config = new HashMap<>();
        if (prettyPrint) {
            config.put(JsonGenerator.PRETTY_PRINTING, null);
        }
        JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(config);
        JavaParserJsonSerializer serializer = new JavaParserJsonSerializer();
        StringWriter jsonWriter = new StringWriter();
        try (JsonGenerator generator = generatorFactory.createGenerator(jsonWriter)) {
            serializer.serialize(node, generator);
        }
        return jsonWriter.toString();
    }

    public static String typeDeclaration2Signature(TypeDeclaration<?> td) {
        if (td instanceof EnumDeclaration) {
            return "enum " + td.getNameAsString();
        }
        if (td instanceof ClassOrInterfaceDeclaration) {
            if (((ClassOrInterfaceDeclaration) td).isInterface()) {
                return "interface " + td.getNameAsString();
            } else {
                return "class " + td.getNameAsString();
            }
        }
        return null;
    }

    /**
     * get directory information which stores the cu, only work for those with storage, not within certain git commit
     *
     * @param cu CompilationUnit
     * @return the path of the directory stores the cu
     */
    public static Path cuDir(CompilationUnit cu) {

        if (cu.getStorage().isPresent()) {
            return cu.getStorage().get().getDirectory();
        } else {
            logger.warn("cu with no storage encountered");
            return null;
        }
    }
}
